'''
Created on 25.10.2011

@author: alex
'''

import logging, ui, traceback, filesys





class OperationNotification(ui.Notification):
	def __init__(self, operation, fileName, status=None):
		ui.Notification.__init__(self, file_name=fileName, operation=operation+' '+fileName, status=status)

	
	
	


class Watcher:
	def __init__(self, fs, cache):
		"""Watcher monitors file system set by fsys 
		for files having been updated (=changed or added) and deleted"""
		self._fs=fs
		self._cache=cache
		self._cacheRealm=cache.getRealm(fs.getRealmUrl())

	def watch(self):
		# all files that are at hands at the moment
		for fileName in self._fs.files():
			fileProps=self._cacheRealm.lookup(fileName) or {}
			# if there is no such file in cache, it has just been created
			if not fileProps.get('stamp') \
			or fileProps['stamp']!=self._fs.stamp(fileName):
				# mark file as created to let synchronizer know about it
				fileProps['stamp']=self._fs.stamp(fileName)
				fileProps['pending']='updated'
				self._cacheRealm.record(fileName, fileProps)
		# all files in cache = files that existed at prevoius watch
		for fileName in self._cacheRealm.find():
			# if file not exists any more, it was deleted
			if not self._fs.stamp(fileName):
				fileProps=self._cacheRealm.lookup(fileName)
				fileProps['pending']='deleted'
			# we must not check stamp, while we have done it in prevous for-loop
	
	def setStamp(self, fileName, stamp):
		self._cacheRealm.record(fileName, {'stamp': stamp})
		
	def forgetFile(self, fileName):
		self._cacheRealm.delete(fileName)
	
	def getDeletedFiles(self):
		return self._cacheRealm.find('deleted')
	
	def getUpdatedFiles(self):
		return self._cacheRealm.find('updated')
	
	def isDeleted(self, fileName):
		return self._cacheRealm.lookup(fileName).get('pending')=='deleted'
	
	def isUpdated(self, fileName): 
		return self._cacheRealm.lookup(fileName).get('pending')=='updated'
	
	def okFile(self, fileName):
		self._cacheRealm.record(fileName, {'pending':None})
		
		
class Synchronizer:
	def __init__(self, local, remote, environment):
		"""args - fs's"""
		self._local=local
		self._remote=remote
		self._cache=environment['cache']
		self._ui=environment['ui']
		self._localWatcher= Watcher(local, self._cache)
		self._remoteWatcher=Watcher(remote, self._cache)
	
	def _upload(self, fileName):
		try:
			self._ui.notify({'code': 'start_uploading_file', 'file_name': fileName})
			stamp=filesys.copy(self._local, self._remote, fileName)
		except:
			logging.error('error uploading file %s:'%fileName)
			self._ui.notify({'code': 'error_uploading_file', 'file_name': fileName})
			if __debug__: traceback.print_exc()
			return False
		else:
			# mark file on remote replica as not updated
			self._localWatcher.okFile(fileName)
			self._remoteWatcher.setStamp(fileName, stamp)
			self._ui.notify({'code': 'finish_uploading_file', 'file_name': fileName})
			return True
		
	def _download(self, fileName):
		try:
			self._ui.notify({'code': 'finish_downloading_file', 'file_name': fileName})
			stamp=filesys.copy(self._remote, self._local, fileName)
		except:
			logging.error('error downloading file %s'%fileName)
			self._ui.notify({'code': 'error_downloading_file', 'file_name': fileName})
			if __debug__: traceback.print_exc()
			return False
		else:
			self._remoteWatcher.okFile(fileName)
			self._localWatcher.setStamp(fileName, stamp)
			self._ui.notify({'code': 'finish_downloading_file', 'file_name': fileName})
			return True	
		
	def _deleteLocal(self, fileName):
		try:
			self._ui.notify({'code': 'start_deleting_local_file', 'file_name': fileName})
			self._local.delete(fileName)
		except:
			logging.info('error deleting file %s'%fileName)
			self._ui.notify({'code': 'error_deleting_local_file', 'file_name': fileName})
			if __debug__: traceback.print_exc()
			return False
		else:
			self._localWatcher.forgetFile(fileName)
			self._localWatcher.forgetFile(fileName)
			self._ui.notify({'code': 'finish_deleting_local_file', 'file_name': fileName})
			return True
		
	def _deleteRemote(self, fileName):
		try:
			self._ui.notify({'code': 'start_deleting_remote_file', 'file_name': fileName})
			self._remote.delete(fileName)
		except:
			logging.info('error deleting file %s'%fileName)
			self._ui.notify({'code': 'error_deleting_remote_file', 'file_name': fileName})
			if __debug__: traceback.print_exc()
			return False
		else:
			self._localWatcher.forgetFile(fileName)
			self._remoteWatcher.forgetFile(fileName)
			self._ui.notify({'code': 'error_deleting_remote_file', 'file_name': fileName})
			return True
		
	def synchronize(self):
		#
		self._localWatcher.watch()
		self._remoteWatcher.watch()

		# upload files updated locally
		for fileName in self._localWatcher.getUpdatedFiles():
			if self._remoteWatcher.isUpdated(fileName) \
			and self._local.hash(fileName)==self._remote.hash(fileName):
				self._remoteWatcher.okFile(fileName)
				self._localWatcher.okFile(fileName)
			else:
				if self._remoteWatcher.isUpdated(fileName):
					ui.defaultInstance(ui.Notification(message='resolve conflicting %s in favour of remote replica'%fileName))
				# just upload
				self._upload(fileName)
				
		# then download files updated remotely
		for fileName in self._remoteWatcher.getUpdatedFiles():
			self._localWatcher.okFile(fileName)
			if self._download(fileName):
				self._remoteWatcher.okFile(fileName)
	
		# files deleted local
		# all updated on both replicas are processed, so conflicts not possible
		for fileName in self._localWatcher.getDeletedFiles():
			self._deleteRemote(fileName)
				
				
		# files deleted remotely
		for fileName in self._remoteWatcher.getDeletedFiles():
			self._deleteLocal(fileName)
				
		
		